import win32gui
import cv2
import pyautogui
import pydirectinput
import numpy as np
import win32api
import yaml
import os
from threading import Thread
from pynput import keyboard
import random
CRASH_NAME = "UE-ShooterGame Game已崩溃，即将关闭"
WIN_NAME = "ArkAscended"
DEBUG_IMG = []
DEBUG_CONFIG = False
DEBUG = False
MAX_CASE = 14
SCAN_INTER = 100
AUTO_BACK = 10
X_OFFSET = 8
Y_OFFSET = 32
REAL_W = 640
REAL_H = 480
ROI_THRESHOLD = 15
RESTART_TIMEOUT = 60
CASE_CLICK = {
    1: {
        "click pos": [{"x": 325, "y": 350}],
        "timeout restart": True,
        "timeout": 300
    },
    2: {
        "click pos": [{"x": 170, "y": 250}],
        "timeout restart": True,
        "timeout": 300
    },
    3: {
        "click pos": [{"x": 160, "y": 170}, {"x": 575, "y": 375}],
        "timeout restart": True,
        "timeout": 300
    },
    4: {
        "click pos": [{"x": 270, "y": 325}, {"x": 60, "y": 355}],
        "timeout restart": True,
        "timeout": 300
    },
    5: {
        "click pos": [{"x": 270, "y": 325}],
        "timeout restart": True,
        "timeout": 300
    },
    6: {
        "click pos": [{"x": 325, "y": 290}],
        "timeout restart": True,
        "timeout": 300
    },
    7: {
        "click pos": [{"x": 140, "y": 400}],
        "timeout restart": True,
        "timeout": 300
    },
    8: {
        "click pos": [],
        "timeout restart": True,
        "timeout": 300
    },
    9: {
        "click pos": [{"x": 270, "y": 325}],
        "timeout restart": True,
        "timeout": 300
    },
    10: {
        "click pos": [{"x": 355, "y": 245}, {"x": 325, "y": 250}],
        "timeout restart": True,
        "timeout": 300
    },
    11: {
        "click pos": [{"x": 270, "y": 325}],
        "timeout restart": True,
        "timeout": 300
    },
    12: {
        "click pos": [{"x": 70, "y": 375}],
        "timeout restart": True,
        "timeout": 300
    },
    13: {
        "click pos": [{"x": 320, "y": 380}],
        "timeout restart": True,
        "timeout": 300
    },
    14: {
        "click pos": [],
        "timeout restart": True,
        "timeout": 300
    }
}

AUTO_BACK_CASE = {
    "case": [8, 3],
    "click pos": [{"x": 60, "y": 350}]
}

roi_exp = [0]
stop = 0
mask_list = [[np.array([]), 0]]
base_img_list = [np.array([])]


def load_cfg():
    global WIN_NAME
    global DEBUG_IMG
    global DEBUG_CONFIG
    global DEBUG
    global MAX_CASE
    global SCAN_INTER
    global AUTO_BACK
    global X_OFFSET
    global Y_OFFSET
    global REAL_W
    global REAL_H
    global ROI_THRESHOLD
    global CASE_CLICK
    global AUTO_BACK_CASE
    global RESTART_TIMEOUT
    file_path = "cfg.yaml"
    if not os.path.exists(file_path):
        with open(file_path, 'w') as file:
            cfg_data = {
                'case cnt': MAX_CASE,
                'config debug': DEBUG_CONFIG,
                'debug image': DEBUG_IMG,
                'runing debug': DEBUG,
                "scanner interval": SCAN_INTER,
                "auto back": AUTO_BACK,
                'x offset': X_OFFSET,
                'y offset': Y_OFFSET,
                'real width': REAL_W,
                'real height': REAL_H,
                'roi threshold': ROI_THRESHOLD,
                'case click': CASE_CLICK,
                'auto back case': AUTO_BACK_CASE,
                'restart timeout': RESTART_TIMEOUT
            }
            yaml.dump(cfg_data, file)
            print(f'文件 {file_path} 已创建。')
    else:
        with open(file_path, 'r') as file:
            try:
                cfg_data = yaml.safe_load(file)
                MAX_CASE = cfg_data['case cnt']
                DEBUG_IMG = cfg_data['debug image']
                DEBUG_CONFIG = cfg_data['config debug']
                DEBUG = cfg_data['runing debug']
                SCAN_INTER = cfg_data['scanner interval']
                AUTO_BACK = cfg_data['auto back']
                X_OFFSET = cfg_data['x offset']
                Y_OFFSET = cfg_data['y offset']
                REAL_W = cfg_data['real width']
                REAL_H = cfg_data['real height']
                ROI_THRESHOLD = cfg_data['roi threshold']
                CASE_CLICK = cfg_data['case click']
                AUTO_BACK_CASE = cfg_data['auto back case']
                RESTART_TIMEOUT = cfg_data['restart timeout']
                print(cfg_data)
            except yaml.YAMLError as e:
                print(e)


def check_windiw():
    win_names = set()

    def get_window_title(window, nouse):
        if win32gui.IsWindow(window) and \
                win32gui.IsWindowEnabled(window) and \
                win32gui.IsWindowVisible(window):
            win_names.add(win32gui.GetWindowText(window))

    win32gui.EnumWindows(get_window_title, 0)
    win_list = [_ for _ in win_names if _]
    if CRASH_NAME in win_list:
        return False
    if WIN_NAME in win_list:
        return True
    return False


def get_win_rect():
    win = win32gui.FindWindow(0, WIN_NAME)  # 根据窗口名称获取窗口对象
    x1, y1, x2, y2 = win32gui.GetWindowRect(win)
    w_max = win32api.GetSystemMetrics(0)
    h_max = win32api.GetSystemMetrics(1)
    if x1 < 0 or y1 < 0 or x2 >= w_max or y2 >= h_max:
        return 0, 0, 0, 0
    x1 += X_OFFSET  # 去除边缘
    y1 += Y_OFFSET  # 去除边缘
    return x1, y1, REAL_W, REAL_H


def get_img(win_rect):
    screenshot = pyautogui.screenshot(region=win_rect)
    screenshot_np = np.array(screenshot)
    img = cv2.resize(screenshot_np, dsize=(640, 480))
    return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)


def get_rect(img):
    x1 = 0
    x2 = 0
    y1 = 0
    y2 = 0
    is_find = False
    for y in range(0, img.shape[0]):
        for x in range(0, img.shape[1]):
            if img[y][x] == 255:
                x1 = x
                y1 = y
                is_find = True
                break
        if is_find:
            break
    is_find = False
    for y in range(img.shape[0] - 1, -1, -1):
        for x in range(img.shape[1] - 1, -1, -1):
            if img[y][x] == 255:
                x2 = x
                y2 = y
                is_find = True
                break
        if is_find:
            break
    if x2 - x1 == 0 or y2 - y1 == 0:
        return 0, 0, 0, 0
    return (x1, y1), (x2, y2)


class RoiExp:
    def __init__(self, p1, p2, content):
        self.p1 = p1
        self.p2 = p2
        self.content = content
        self.thr = 0  # 如果阈值高于该值认为内容不为期望


def get_roi_diff_mean(img, idx=0):
    img_roi = img * mask_list[idx][0]
    sub = np.absolute(img_roi.astype(np.int8) - base_img_list[idx].astype(np.int8)).astype(np.uint8)
    if DEBUG and idx in DEBUG_IMG:
        cv2.imshow(f"sub{idx}", sub)
    return sub.sum() / mask_list[idx][1]


def check_case(img, care_case):
    tmp = []
    ret = []
    dbg_mean = []
    for i in care_case:
        m = get_roi_diff_mean(img, i)
        if DEBUG:
            dbg_mean.append(m)
        if m < ROI_THRESHOLD:
            tmp.append((i, m))
    tmp.sort(key=lambda ele: ele[1])
    for t in tmp:
        ret.append(t[0])
    if DEBUG:
        s = ""
        for i in range(0, len(dbg_mean)):
            s += "%d=%.2f " % (care_case[i], dbg_mean[i])
        print("ROI均值距离 " + s)
    return ret


def get_mask_rect():  # 根据mask获取roi矩形和像素值
    global mask_list
    for i in range(1, MAX_CASE + 1):
        img_name = f"img/{i}.png"
        mask_name = f"mask/{i}.png"
        img = cv2.imread(img_name, 0)
        mask = cv2.imread(mask_name, 0)
        if img is None:
            print("找不到", img_name)
        if mask is None:
            print("找不到", mask_name)
        _, mask = cv2.threshold(mask, 127, 1, cv2.THRESH_BINARY)
        mask_list.append([mask, mask.sum()])
        base_img_list.append(img * mask)
        if DEBUG_CONFIG and i in DEBUG_IMG:
            cv2.imshow("roi", base_img_list[i])
            cv2.waitKey(0)
            cv2.destroyAllWindows()


# def get_roi_thr():
#     # 将roi的矩形套用在其他场景下，确定阈值，以提高健壮性
#     for i in range(1, MAX_CASE + 1):
#         mean = []
#         for j in range(1, MAX_CASE + 1):
#             if i == j:
#                 continue
#             cmp_name = f"img/{j}.png"
#             cmp = cv2.imread(cmp_name, 0)
#             cmp_roi = cmp[roi_exp[i].p1[1]:roi_exp[i].p2[1], roi_exp[i].p1[0]:roi_exp[i].p2[0]]
#             cmp_roi = cmp_roi.astype(np.int8)
#             sub = np.absolute(cmp_roi.astype(np.int8) - roi_exp[i].content.astype(np.int8)).astype(np.uint8)
#             m = np.mean(sub)
#             mean.append(get_roi_diff_mean(cmp, roi_exp[i]))
#             if DEBUG_CONFIG and i in DEBUG_IMG:
#                 img_name = f"img/{i}.png"
#                 img = cv2.imread(img_name, 0)
#                 cv2.rectangle(img, roi_exp[i].p1, roi_exp[i].p2, 255, 1, 1)
#                 cv2.imshow("img", img)
#
#                 print(f"{cmp_name} 与 {f"img/{i}.png"} 距离均值为{m}")
#                 cv2.rectangle(cmp, roi_exp[i].p1, roi_exp[i].p2, 255, 1, 1)
#                 cv2.imshow("cmp", cmp)
#                 cv2.waitKey(0)
#                 cv2.destroyAllWindows()
#         roi_exp[i].thr = min(mean) * 0.8
#         if DEBUG_CONFIG:
#             print(f"img/{i}.png 阈值为{roi_exp[i].thr}")
#     if DEBUG:
#         for i in range(1, len(roi_exp)):
#             print(f"img/{i}.png 阈值为{roi_exp[i].thr}")


def click(base_x, base_y, case_item):
    for case_pos in case_item["click pos"]:
        x = case_pos["x"]
        y = case_pos["y"]
        pydirectinput.click(base_x + int(float(x) / 640 * REAL_W), base_y + int(float(y) / 480 * REAL_H))


def run():
    print("run")
    print("power by lzs")
    last_case = -1
    time_cnt = 0
    time_cnt_s = 0
    while True:
        try:
            if not check_windiw():
                print("kill asa ... ")
                os.system("taskkill /f /t /im ArkAscended.exe")
                print("start asa ...")
                os.popen("Start steam://rungameid/2399830")
                for i in range(0, RESTART_TIMEOUT):
                    cv2.waitKey(1000)
                    if check_windiw():
                        break
                    print(f"wait restart rimeout in {RESTART_TIMEOUT - i}s")
                continue
            if stop == 2:
                break
            elif stop == 1:
                cv2.waitKey(SCAN_INTER)
                continue
            cv2.waitKey(SCAN_INTER)
            win_rect = get_win_rect()
            if win_rect == (0, 0, 0, 0):
                print("应用窗口请勿靠近屏幕边缘")
                continue
            basex = win_rect[0]
            basey = win_rect[1]
            img = get_img(win_rect)
            case = check_case(img, [n for n in range(1, MAX_CASE + 1)])
            if len(case) == 0:
                time_cnt = 0
                # 防t
                click(basex, basey,
                      {"click pos": [{"x": 10 + random.randint(0, 50),
                                      "y": 10 + random.randint(0, 50)}]})
                keys = ['w', 's', 'd', 'a']
                idx = random.randint(0, len(keys) - 1)
                op = random.randint(0, 3)
                if op == 1:
                    pydirectinput.keyDown(keys[idx])
                else:
                    pydirectinput.keyUp(keys[idx])
                continue
            click(basex, basey, CASE_CLICK[case[0]])
            if case[0] in AUTO_BACK_CASE["case"]:
                if time_cnt >= 1000 * AUTO_BACK:
                    click(basex, basey, AUTO_BACK_CASE)
                elif time_cnt / 1000 > time_cnt_s and (AUTO_BACK - time_cnt / 1000) < 60:
                    print(f"auto back in {AUTO_BACK - time_cnt / 1000}s")

            time_cnt += SCAN_INTER
            if last_case != case[0]:
                time_cnt = 0

            if CASE_CLICK[case[0]]["timeout restart"]:
                if time_cnt >= 1000 * CASE_CLICK[case[0]]["timeout"]:
                    print("restart asa ... ")
                    os.system("taskkill /f /t /im ArkAscended.exe")
                elif int(time_cnt / 1000) > time_cnt_s and (CASE_CLICK[case[0]]["timeout"] - time_cnt / 1000) < 60:
                    print(f"restart asa at {CASE_CLICK[case[0]]["timeout"] - time_cnt / 1000}s")

            time_cnt_s = time_cnt / 1000
            last_case = case[0]
            if DEBUG:
                print(case)
        except Exception as e:
            print("出现异常，不关键，能跑就行，不能跑x了重新开")
            print(e)


def keyboard_Listen():
    def key_release(key):
        global stop
        if key == keyboard.Key.esc:
            stop = 2
            if DEBUG:
                print("退出")
            exit()
        elif key == keyboard.Key.ctrl_l:
            print("暂停")
            stop = 1
        elif key == keyboard.Key.alt_l:
            print("继续")
            stop = 0

    with keyboard.Listener(on_release=key_release) as listener:
        listener.join()


load_cfg()
get_mask_rect()
# get_roi_thr()
th1 = Thread(target=run)
th2 = Thread(target=keyboard_Listen)
th1.start()
th2.start()
th1.join()
